package agkey

import (
	crand "crypto/rand"
	encoding "encoding/ascii85"
	"encoding/base64"
	"encoding/binary"
	"log"
	"os"

	ko "gitee.com/tgodfather/utility/crypt/options"
	"github.com/tjfoc/gmsm/sm2"
	"github.com/tjfoc/gmsm/x509"
)

var (
	// func_enc_pri = x509.WritePrivateKeyToHex
	// func_dec_pri = x509.ReadPrivateKeyFromHex
	func_enc_pri = MarshalSm2PrivateKey
	func_dec_pri = ParsePKCS8PrivateKey

	func_enc_pub = MarshalSm2PublicKey
	func_dec_pub = ParseSm2PublicKey

	pri_key_enc_len = 200
	pub_key_enc_len = 124
)

// func WritePrivateKeyToHex(key *sm2.PrivateKey) string {

// 	data := key.D.Bytes()

// 	//return base64.StdEncoding.EncodeToString(data)
// 	return hex.EncodeToString(data)
// }

// func ReadPrivateKeyFromHex(keystr string) (*sm2.PrivateKey, error) {

// 	return x509.ReadPrivateKeyFromHex(keystr)
// }

// ////////////////////////////////////////////////////
func MarshalSm2PrivateKey(key *sm2.PrivateKey) string {

	data, err := x509.MarshalSm2PrivateKey(key, nil)
	if err != nil {
		return ""
	}

	return base64.StdEncoding.EncodeToString(data)
}

func ParsePKCS8PrivateKey(dHex string) (*sm2.PrivateKey, error) {
	//data, _ := hex.DecodeString(dHex)
	data, _ := base64.StdEncoding.DecodeString(dHex)
	key, err := x509.ParsePKCS8PrivateKey(data, nil)
	return key, err
}

// //////////////////////////////////////////////
func MarshalSm2PublicKey(key *sm2.PublicKey) string {

	data, err := x509.MarshalSm2PublicKey(key)
	if err != nil {
		return ""
	}

	return base64.StdEncoding.EncodeToString(data)
}

func ParseSm2PublicKey(dHex string) (*sm2.PublicKey, error) {
	//data, _ := hex.DecodeString(dHex)
	data, _ := base64.StdEncoding.DecodeString(dHex)
	key, err := x509.ParseSm2PublicKey(data)
	return key, err
}

type AgkeyGenerate struct {
	Key agkey
}

func int32ToBytes(i int32) []byte {
	buf := make([]byte, 4)
	binary.BigEndian.PutUint32(buf, uint32(i))
	return buf
}

func keyId(id int32) string {
	index := id
	src := int32ToBytes(index)
	dst := make([]byte, encoding.MaxEncodedLen(4))
	encoding.Encode(dst, src)
	return string(dst)
}

// func keyDecode(keyStr string) int32 {
// 	dst := make([]byte, 4)
// 	encoding.Decode(dst, []byte(keyStr))
// }

//	func WritePrivateKeyToHex(key *sm2.PrivateKey) string {
//		// return key.D.Text(16)
//		// key.D.FillBytes()
//	}

// 构建或者获取key的函数， 返回kid 与 key
type KeyFunction func(num int32, opts *ko.KeyOptions) (keyId string, key any, err error)

func CreateAgKey(maxNum int32, KeyFun KeyFunction, opts ...ko.KeyOption) (newKey *agkey, err error) {

	if KeyFun == nil {
		return nil, ERR_AGKEY_INVALID_PARAM
	}

	keyOpts := ko.NewKeyOptions(opts...)

	newKey = newAgKey()
	for i := range maxNum {
		kid, key, kErr := KeyFun(i, keyOpts)
		if kErr != nil {
			err = kErr
			break
		}
		if key == nil {
			//key 为空，表示不能再产生新的key，不报错，正常返回
			break
		}

		nk := item{}
		nk.Kid = kid
		nk.KType = keyOpts.KeyType
		// nk.CKey = key
		switch nk.KType {
		case ko.KT_SM2_PRI:
			if ckey, ok := key.(*sm2.PrivateKey); ok {
				nk.Cipher = func_enc_pri(ckey)
				nk.CKey = ckey
			} else {
				err = ERR_AGKEY_UNEXCEPTED_KEY
			}
		case ko.KT_SM2_PUB:
			if ckey, ok := key.(*sm2.PublicKey); ok {
				nk.Cipher = func_enc_pub(ckey)
				nk.CKey = ckey
			} else {
				err = ERR_AGKEY_UNEXCEPTED_KEY
			}
		default:
			err = ERR_AGKEY_KEY_UNSUPPORT
		}
		if err != nil {
			break
		}
		newKey.Add(nk)
	}

	if err == nil && keyOpts.Writer != nil {
		if newKey.Length() > 0 {
			newKey.Save(ko.WithWriter(keyOpts.Writer))
		}
	}

	return newKey, err
}

func GeneratePriAgKey(num int32, cipher []byte, outfile string) error {

	kf := func(id int32, opts *ko.KeyOptions) (kId string, key any, err error) {

		key, _ = sm2.GenerateKey(crand.Reader)
		kId = keyId(id)
		return
	}

	// newKey := newAgKey()
	// for i := range num {

	// 	ckey, _ := sm2.GenerateKey(crand.Reader)
	// 	// time.Sleep(10 * time.Millisecond)
	// 	id := i

	// 	nk := item{}
	// 	nk.Kid = keyId(id)
	// 	nk.KType = ko.KT_SM2_PRI
	// 	nk.Cipher = func_enc_pri(ckey)

	// 	nk.CKey = ckey
	// 	// if len(nk.Cipher) != pri_key_enc_len {
	// 	// 	log.Printf("----bad ckey i[%d] lenCipher(%v) keyD(%v) [%v]", i, len(nk.Cipher), len(ckey.D.Bytes()), nk)
	// 	// }

	// 	newKey.Add(nk)

	// }
	// x509.WritePublicKeyToHex()
	fn := outfile
	fi, _ := os.Create(fn)
	defer fi.Close()

	_, err := CreateAgKey(num, kf, ko.WithKeyType(ko.KT_SM2_PRI), ko.WithWriter(fi))

	return err
}

func ExportPubAgKey(priKeyfile string, outfile string) error {
	priAgKey := newAgKey()
	infile, err := os.Open(priKeyfile)
	if err != nil {
		log.Println("open prikey file fail", err)
		return err
	}
	defer infile.Close()

	err = priAgKey.Load(nil, ko.WithReader(infile))
	if err != nil {
		log.Println("open prikey file fail", err, infile)
		return err
	}
	var itarray []*item
	for _, it := range priAgKey.keyMap {
		itarray = append(itarray, &it)
	}

	kf := func(id int32, opts *ko.KeyOptions) (kId string, key any, err error) {
		if len(itarray) > int(id) {
			it := itarray[id]

			if sm2pri := it.CKey.(*sm2.PrivateKey); sm2pri != nil {
				kId = it.Kid
				key = &sm2pri.PublicKey
			} else {
				log.Println("sm2pri is null", it.Kid, it.KType, it.CKey, it.Cipher)
				err = ERR_AGKEY_NOT_FOUND
			}
		}
		return
	}

	// pubAgKey := newAgKey()
	// for i, it := range priAgKey.keyMap {

	// 	if sm2pri := it.CKey.(*sm2.PrivateKey); sm2pri != nil {

	// 		sm2pub := &sm2pri.PublicKey

	// 		nk := item{}
	// 		nk.Kid = it.Kid
	// 		nk.KType = ko.KT_SM2_PUB
	// 		nk.Cipher = func_enc_pub(sm2pub)
	// 		nk.CKey = sm2pub
	// 		if len(nk.Cipher) != pub_key_enc_len {
	// 			log.Printf("----bad ckey i[%v] lenCipher(%v)  [%v]\n", i, len(nk.Cipher), nk)
	// 		}
	// 		pubAgKey.Add(nk)
	// 		// log.Println("add ", nk.Kid)
	// 	} else {
	// 		log.Println("sm2pri is null", it.Kid, it.KType, it.CKey, it.Cipher)
	// 	}
	// }
	// x509.WritePublicKeyToHex()
	fn := outfile
	fi, err := os.Create(fn)
	if err != nil {
		log.Println("Create pubkey file fail", err, fn)
		return err
	}
	defer fi.Close()

	_, err = CreateAgKey(int32(len(itarray)), kf, ko.WithKeyType(ko.KT_SM2_PUB), ko.WithWriter(fi))
	// pubAgKey.Save(ko.WithWriter(fi))
	return err
}
